Micron Document
NexusPi Git Node

Node / mirrors / RTNode-HeltecV4 / files / FirewallConfig.h

Displaying Raw • Download

FirewallConfig.h main (ee519cc2) Text, 45.47 KB

T8b949e// Copyright (C) 2026, Firewall Mode Extension
T8b949e// Based on microReticulum_Firmware by Mark Qvist
T8b949e//
T8b949e// FirewallConfig.h — Captive-portal web configuration for the legacy
T8b949e// "Firewall Mode" path.
T8b949e// together with the rest of the boundary-mode terminology. In this fork,
T8b949e// Firewall Mode is the only intended mode of operation in this fork.
T8b949e// When triggered (first boot with no config, or button hold >5s),
T8b949e// the device starts a WiFi AP with a web form for all settings:
T8b949e// WiFi STA credentials, TCP backbone params, LoRa radio params,
T8b949e// and optional AP-mode TCP server.
T8b949e//
T8b949e// This program is free software: you can redistribute it and/or modify
T8b949e// it under the terms of the GNU General Public License as published by
T8b949e// the Free Software Foundation, either version 3 of the License, or
T8b949e// (at your option) any later version.

Tff7b72#Tff7b72ifndef FIREWALL_CONFIG_H
Tff7b72#Tff7b72define FIREWALL_CONFIG_H

Tff7b72#Tff7b72ifdef FIREWALL_MODE

Tff7b72#Tff7b72include T8b949e<WiFi.h>
Tff7b72#Tff7b72include T8b949e<WebServer.h>
Tff7b72#Tff7b72include T8b949e<DNSServer.h>
Tff7b72#Tff7b72include T8b949e"MdnsService.h"

T8b949e// ─── Node hash (cached in RTC by normal boot, read here without starting RNS) ─
Tff7b72#Tff7b72define NODE_HASH_RTC_MAGIC 0x504B4841UL
Tff7b72extern Tffa657uint32_t Te6edf3rtc_node_hash_magicTb4b4b4;
Tff7b72extern Tffa657char Te6edf3rtc_node_hash_hexTb4b4b4[T79c0ff33Tb4b4b4]Tb4b4b4;
Tff7b72extern Tffa657char Te6edf3rtc_probe_hash_hexTb4b4b4[T79c0ff21Tb4b4b4]Tb4b4b4;

Tff7b72#Tff7b72define BOUNDARY_RESET_REPORT_MAGIC 0x42525054UL
Tff7b72#Tff7b72define BOUNDARY_RESET_REPORT_VERSION 1

Tff7b72enum Te6edf3BoundaryResetCause Tff7b72: Tffa657uint8_t Tb4b4b4{
Te6edf3BOUNDARY_RESET_CAUSE_NONE Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3BOUNDARY_RESET_CAUSE_HEAP_WATCHDOG Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3BOUNDARY_RESET_CAUSE_WIFI_WATCHDOG Tff7b72= T79c0ff2Tb4b4b4,
Tb4b4b4}Tb4b4b4;

Tff7b72enum Te6edf3BoundaryHeapPressureStage Tff7b72: Tffa657uint8_t Tb4b4b4{
Te6edf3BOUNDARY_HEAP_STAGE_NONE Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3BOUNDARY_HEAP_STAGE_SHED Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3BOUNDARY_HEAP_STAGE_TRIM Tff7b72= T79c0ff2Tb4b4b4,
Tb4b4b4}Tb4b4b4;

Tff7b72struct T56d364BoundaryResetReport Tb4b4b4{
Tffa657uint32_t Te6edf3magicTb4b4b4;
Tffa657uint8_t Te6edf3versionTb4b4b4;
Tffa657uint8_t Te6edf3causeTb4b4b4;
Tffa657uint8_t Te6edf3heap_stageTb4b4b4;
Tffa657uint8_t Te6edf3observed_reset_reasonTb4b4b4;
Tffa657uint32_t Te6edf3uptime_msTb4b4b4;
Tffa657uint32_t Te6edf3free_heapTb4b4b4;
Tffa657uint32_t Te6edf3min_free_heapTb4b4b4;
Tffa657uint32_t Te6edf3max_alloc_heapTb4b4b4;
Tffa657int32_t Te6edf3wifi_statusTb4b4b4;
Tffa657uint16_t Te6edf3path_table_maxsizeTb4b4b4;
Tffa657uint16_t Te6edf3path_table_maxpersistTb4b4b4;
Tffa657uint32_t Te6edf3bridged_lora_to_tcpTb4b4b4;
Tffa657uint32_t Te6edf3bridged_tcp_to_loraTb4b4b4;
Tb4b4b4}Tb4b4b4;

Tff7b72extern Te6edf3BoundaryResetReport Te6edf3boundary_reset_reportTb4b4b4;
Tffa657bool Td2a8ffboundary_reset_report_availableTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72const Tffa657charTff7b72* Td2a8ffboundary_reset_cause_labelTb4b4b4(Tffa657uint8_t Te6edf3causeTb4b4b4)Tb4b4b4;
Tff7b72const Tffa657charTff7b72* Td2a8ffboundary_heap_stage_labelTb4b4b4(Tffa657uint8_t Te6edf3stageTb4b4b4)Tb4b4b4;
Tff7b72const Tffa657charTff7b72* Td2a8ffboundary_reset_reason_labelTb4b4b4(Tffa657uint8_t Te6edf3reasonTb4b4b4)Tb4b4b4;

T8b949e// ─── Config Portal State ─────────────────────────────────────────────────────
Tff7b72static Tffa657bool Te6edf3config_portal_active Tff7b72= Tffa657falseTb4b4b4;
Tff7b72static Te6edf3WebServerTff7b72* Te6edf3config_server Tff7b72= Tffa657nullptrTb4b4b4;
Tff7b72static Te6edf3DNSServerTff7b72* Te6edf3config_dns Tff7b72= Tffa657nullptrTb4b4b4;

Tff7b72static Tff7b72const Tffa657char Te6edf3CONFIG_AP_SSIDTb4b4b4[Tb4b4b4] Tff7b72= Ta5d6ff"Ta5d6ffRTNode-SetupTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657uint16_t Te6edf3DNS_PORT Tff7b72= T79c0ff53Tb4b4b4;
Tff7b72static Tff7b72const Tffa657uint16_t Te6edf3HTTP_PORT Tff7b72= T79c0ff80Tb4b4b4;

T8b949e// Forward declarations
Tffa657void Td2a8ffconfig_portal_startTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffconfig_portal_stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657void Td2a8ffconfig_portal_loopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657bool Td2a8ffconfig_portal_is_activeTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657bool Td2a8ffboundary_needs_configTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// ─── Common bandwidth values (Hz) ───────────────────────────────────────────
T8b949e// These match Reticulum standard channel plans
T8b949e// Stored in flash (PROGMEM) to save ~200 bytes of RAM
Tff7b72static Tff7b72const Tffa657uint32_t Te6edf3BW_OPTIONS_HZTb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Tb4b4b4{
T79c0ff7800Tb4b4b4, T79c0ff10400Tb4b4b4, T79c0ff15600Tb4b4b4, T79c0ff20800Tb4b4b4, T79c0ff31250Tb4b4b4, T79c0ff41700Tb4b4b4, T79c0ff62500Tb4b4b4, T79c0ff125000Tb4b4b4, T79c0ff250000Tb4b4b4, T79c0ff500000Tb4b4b4,
Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_0Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff7.8 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_1Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff10.4 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_2Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff15.6 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_3Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff20.8 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_4Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff31.25 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_5Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff41.7 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_6Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff62.5 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_7Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff125 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_8Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff250 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657char Te6edf3BW_LABEL_9Tb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Ta5d6ff"Ta5d6ff500 kHzTa5d6ff"Tb4b4b4;
Tff7b72static Tff7b72const Tffa657charTff7b72* Tff7b72const Te6edf3BW_OPTIONS_LABELSTb4b4b4[Tb4b4b4] Te6edf3PROGMEM Tff7b72= Tb4b4b4{
Te6edf3BW_LABEL_0Tb4b4b4, Te6edf3BW_LABEL_1Tb4b4b4, Te6edf3BW_LABEL_2Tb4b4b4, Te6edf3BW_LABEL_3Tb4b4b4, Te6edf3BW_LABEL_4Tb4b4b4,
Te6edf3BW_LABEL_5Tb4b4b4, Te6edf3BW_LABEL_6Tb4b4b4, Te6edf3BW_LABEL_7Tb4b4b4, Te6edf3BW_LABEL_8Tb4b4b4, Te6edf3BW_LABEL_9Tb4b4b4,
Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657int Te6edf3BW_OPTIONS_COUNT Tff7b72= Tff7b72sizeofTb4b4b4(Te6edf3BW_OPTIONS_HZTb4b4b4) Tff7b72/ Tff7b72sizeofTb4b4b4(Te6edf3BW_OPTIONS_HZTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4)Tb4b4b4;

Tff7b72static Tffa657uint8_t Td2a8ffconfig_default_display_rotationTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72#Tff7b72if BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_RAK4631
Tff7b72return T79c0ff0Tb4b4b4;
Tff7b72#Tff7b72elif BOARD_MODEL == BOARD_HELTEC32_V2 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_TBEAM_S_V1
Tff7b72return T79c0ff1Tb4b4b4;
Tff7b72#Tff7b72else
Tff7b72return T79c0ff3Tb4b4b4;
Tff7b72#Tff7b72endif
Tb4b4b4}

T8b949e// ─── HTML Page Generation ────────────────────────────────────────────────────

Tff7b72static Tffa657void Td2a8ffconfig_send_htmlTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Read current values from EEPROM/globals for pre-population
Tffa657char Te6edf3cur_ssidTb4b4b4[T79c0ff33Tb4b4b4] Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4;
Tffa657char Te6edf3cur_pskTb4b4b4[T79c0ff33Tb4b4b4] Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4;

Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3cur_ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Tb4b4b4(Tffa657charTb4b4b4)T79c0ff0xFFTb4b4b4) Te6edf3cur_ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Te6edf3cur_ssidTb4b4b4[T79c0ff32Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;

Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3cur_pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_PSK Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Tb4b4b4(Tffa657charTb4b4b4)T79c0ff0xFFTb4b4b4) Te6edf3cur_pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Te6edf3cur_pskTb4b4b4[T79c0ff32Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;

T8b949e// Current LoRa values (from globals, which were loaded from EEPROM)
Tffa657uint32_t Te6edf3cur_freq Tff7b72= Te6edf3lora_freqTb4b4b4;
Tffa657uint32_t Te6edf3cur_bw Tff7b72= Te6edf3lora_bwTb4b4b4;
Tffa657int Te6edf3cur_sf Tff7b72= Te6edf3lora_sfTb4b4b4;
Tffa657int Te6edf3cur_cr Tff7b72= Te6edf3lora_crTb4b4b4;
Tffa657int Te6edf3cur_txp Tff7b72= Te6edf3lora_txpTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_txp Tff7b72=Tff7b72= T79c0ff0xFFTb4b4b4) Te6edf3cur_txp Tff7b72= Te6edf3PA_MAX_OUTPUTTb4b4b4; T8b949e// Default to board max

T8b949e// Default frequency if not set
Tff7b72if Tb4b4b4(Te6edf3cur_freq Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3cur_freq Tff7b72= T79c0ff914875000Tb4b4b4; T8b949e// 914.875 MHz default
Tff7b72if Tb4b4b4(Te6edf3cur_bw Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3cur_bw Tff7b72= T79c0ff125000Tb4b4b4; T8b949e// 125 kHz default
Tff7b72if Tb4b4b4(Te6edf3cur_sf Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3cur_sf Tff7b72= T79c0ff10Tb4b4b4; T8b949e// SF10 default
Tff7b72if Tb4b4b4(Te6edf3cur_cr Tff7b72< T79c0ff5 Tff7b72|Tff7b72| Te6edf3cur_cr Tff7b72> T79c0ff8Tb4b4b4) Te6edf3cur_cr Tff7b72= T79c0ff5Tb4b4b4; T8b949e// CR 4/5 default

T8b949e// Build the HTML page
Te6edf3String Te6edf3html Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<!DOCTYPE html><html><head>Ta5d6ff"
Ta5d6ff"Ta5d6ff<meta name='viewport' content='width=device-width,initial-scale=1'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<title>RTNode Setup</title>Ta5d6ff"
Ta5d6ff"Ta5d6ff<style>Ta5d6ff"
Ta5d6ff"Ta5d6ffbody{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;margin:0;padding:16px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffh1{color:#e94560;font-size:1.4em;margin:0 0 8px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffh2{color:#0f3460;background:#e0e0e0;padding:6px 10px;margin:18px -10px 10px;font-size:1em;border-radius:4px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffform{max-width:480px;margin:0 auto;}Ta5d6ff"
Ta5d6ff"Ta5d6fflabel{display:block;margin:8px 0 2px;font-size:0.9em;color:#aaa;}Ta5d6ff"
Ta5d6ff"Ta5d6ffinput,select{width:100%;padding:8px;margin:2px 0 6px;box-sizing:border-box;Ta5d6ff"
Ta5d6ff"Ta5d6ffbackground:#16213e;border:1px solid #0f3460;color:#e0e0e0;border-radius:4px;font-size:0.95em;}Ta5d6ff"
Ta5d6ff"Ta5d6ffinput:focus,select:focus{border-color:#e94560;outline:none;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.row{display:flex;gap:10px;}.row>div{flex:1;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.note{font-size:0.8em;color:#666;margin:2px 0 8px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffbutton{width:100%;padding:12px;margin:20px 0;background:#e94560;color:#fff;Ta5d6ff"
Ta5d6ff"Ta5d6ffborder:none;border-radius:4px;font-size:1.1em;cursor:pointer;}Ta5d6ff"
Ta5d6ff"Ta5d6ffbutton:hover{background:#c73e54;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.ok{background:#16213e;padding:20px;border-radius:8px;text-align:center;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.ok h1{color:#0f0;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.node-hash{background:#0f1a30;border:1px solid #0f3460;border-radius:6px;Ta5d6ff"
Ta5d6ff"Ta5d6ffpadding:10px 14px;margin:0 0 16px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.node-hash .nh-label{display:block;font-size:0.75em;color:#888;margin-bottom:4px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.node-hash code{font-family:monospace;font-size:0.95em;color:#7ecfff;Ta5d6ff"
Ta5d6ff"Ta5d6ffword-break:break-all;letter-spacing:0.05em;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.reset-report{background:#241d12;border:1px solid #6b4f1d;border-radius:6px;Ta5d6ff"
Ta5d6ff"Ta5d6ffpadding:12px 14px;margin:0 0 16px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.reset-report .rr-title{display:block;font-size:0.8em;color:#f4c87a;margin-bottom:8px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.reset-report .rr-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 12px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.reset-report .rr-key{font-size:0.78em;color:#b8a98b;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.reset-report .rr-val{font-family:monospace;font-size:0.9em;color:#f8e7c2;word-break:break-word;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.reset-report .rr-note{font-size:0.78em;color:#b8a98b;margin-top:10px;}Ta5d6ff"
Ta5d6ff"Ta5d6ff</style></head><body>Ta5d6ff"
Ta5d6ff"Ta5d6ff<h1>&#x1f4e1; RTNode</h1>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

T8b949e// ── Node public hash ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='node-hash'><span class='nh-label'>&#x1f511; Node Hash (Reticulum destination)</span><code>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rtc_node_hash_magic Tff7b72=Tff7b72= Te6edf3NODE_HASH_RTC_MAGIC Tff7b72&Tff7b72& Te6edf3rtc_node_hash_hexTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3rtc_node_hash_hexTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<span style='color:#888;font-style:italic;'>Not yet assigned &mdash; will be set on first normal boot</span>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</code></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3boundary_reset_report_availableTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='reset-report'><span class='rr-title'>&#x26a0; Last automatic reset report</span><div class='rr-grid'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Trigger</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_cause_labelTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3causeTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Observed reset</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reason_labelTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3observed_reset_reasonTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3observed_reset_reason Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff (Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657unsignedTb4b4b4)Te6edf3boundary_reset_reportTb4b4b4.Te6edf3observed_reset_reasonTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff)Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Heap stage</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_heap_stage_labelTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3heap_stageTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Uptime at reset</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3uptime_ms Tff7b72/ T79c0ff1000ULTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff s</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Free heap</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3free_heapTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff B</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Min free heap</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3min_free_heapTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff B</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Max alloc heap</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3max_alloc_heapTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff B</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>WiFi status</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3wifi_statusTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Path caps</div><div class='rr-val'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3path_table_maxsizeTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3path_table_maxpersistTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><div class='rr-key'>Bridge counters</div><div class='rr-val'>L-&gt;T Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3bridged_lora_to_tcpTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff / T-&gt;L Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3boundary_reset_reportTb4b4b4.Te6edf3bridged_tcp_to_loraTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div><div class='rr-note'>Captured before an automatic reboot. Clears on power cycle.</div></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<form method='POST' action='/save'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Node Name Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f3f7; Node Name</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>A human-readable name for this node, shown in advertisements and on maps Ta5d6ff"
Ta5d6ff"Ta5d6ffsuch as <a href='https://rmap.world' target='_blank' style='color:#7ecfff'>rmap.world</a>. Ta5d6ff"
Ta5d6ff"Ta5d6ffLeave blank to auto-generate a name from the node hash.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Name</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='node_name' maxlength='32' placeholder='e.g. My RNode' value='Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3node_nameTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4f6; WiFi Network</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>WiFi</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='wifi_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3wifi_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3wifi_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled (LoRa-only repeater)</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<label>SSID</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='ssid' maxlength='32' placeholder='Your WiFi network' value='Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3cur_ssidTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Password</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='psk' type='password' maxlength='32' placeholder='WiFi password' value='Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3cur_pskTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── TCP Backbone Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f310; TCP Backbones</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Configure up to four independent TCP backbone uplinks. Ta5d6ff"
Ta5d6ff"Ta5d6ffEach enabled slot becomes its own boundary interface. Leave host blank to disable a slot.</p>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3slot Tff7b72= T79c0ff0Tb4b4b4; Te6edf3slot Tff7b72< Te6edf3FIREWALL_BACKBONE_SLOTSTb4b4b4; Te6edf3slotTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='card'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ffBackbone Ta5d6ff"Tb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3slot Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<select name='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ffbbTa5d6ff"Tb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3slotTb4b4b4) Tff7b72+ Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff_en'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option></select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Host</label><input name='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ffbbTa5d6ff"Tb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3slotTb4b4b4) Tff7b72+ Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff_host' maxlength='63' placeholder='e.g. rns.example.net' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3hostTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Port</label><input name='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ffbbTa5d6ff"Tb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3slotTb4b4b4) Tff7b72+ Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff_port' type='number' min='1' max='65535' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3portTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ── Local TCP Server Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4e1; Local TCP Server (optional)</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Run a TCP server on the same WiFi network so local devices can connect. Ta5d6ff"
Ta5d6ff"Ta5d6ffUses Gateway mode (forwards announces to and from local TCP clients). Ta5d6ff"
Ta5d6ff"Ta5d6ffConnect endpoint clients only; disable Reticulum transport mode on Meshchat or other LAN clients.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Local TCP Server</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='ap_tcp_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3ap_tcp_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ap_tcp_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>TCP Port</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='ap_tcp_port' type='number' min='1' max='65535' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ap_tcp_portTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<label>Local Network Name (mDNS)</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Publishes the device on the local network so you can reach it as Ta5d6ff"
Ta5d6ff"Ta5d6ff<code>&lt;name&gt;.local</code> from any computer in your LAN without knowing its IP. Ta5d6ff"
Ta5d6ff"Ta5d6ffThis applies to the node itself and local TCP access, even if the TCP server is disabled.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='mdns_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<label>mDNS Hostname</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Leave blank for the default <code>rtnode&lt;XXXX&gt;.local</code> Ta5d6ff"
Ta5d6ff"Ta5d6ff(last 4 hex chars of the device MAC). Ta5d6ff"
Ta5d6ff"Ta5d6ffAllowed: lowercase letters, digits and hyphens; first/last char must be alphanumeric.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='mdns_name' maxlength='32' placeholder='rtnode' value='Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_hostnameTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Diagnostics / rnprobe Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f50d; Diagnostics (rnprobe)</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>When enabled, this node responds to <code>rnprobe</code> utility pings Ta5d6ff"
Ta5d6ff"Ta5d6fffrom other Reticulum nodes. rnprobe measures round-trip time and packet loss over Ta5d6ff"
Ta5d6ff"Ta5d6ffthe network path. The probe destination is identity-backed and requires no link setup.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>rnprobe Responder</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='probe_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3probe_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3probe_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='node-hash' style='margin-top:8px;'><span class='nh-label'>&#x1f4e8; Probe Destination</span><code>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3rtc_node_hash_magic Tff7b72=Tff7b72= Te6edf3NODE_HASH_RTC_MAGIC Tff7b72&Tff7b72& Te6edf3rtc_probe_hash_hexTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3rtc_probe_hash_hexTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<span style='color:#888;font-style:italic;'>Available after first normal boot</span>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</code></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Run: <code>rnprobe rnstransport.probe &lt;hash&gt;</code> Ta5d6ff"
Ta5d6ff"Ta5d6fffrom any reachable Reticulum node. The hash is also printed in the serial log Ta5d6ff"
Ta5d6ff"Ta5d6ffas <code>PROBE-DST</code> at boot.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── LoRa Radio Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4fb; LoRa Radio</h2>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

T8b949e// Frequency — show in MHz for human-friendliness
Tffa657char Te6edf3freq_strTb4b4b4[T79c0ff16Tb4b4b4]Tb4b4b4;
Te6edf3dtostrfTb4b4b4(Tb4b4b4(Tffa657doubleTb4b4b4)Te6edf3cur_freq Tff7b72/ T79c0ff1000000.0Tb4b4b4, T79c0ff1Tb4b4b4, T79c0ff3Tb4b4b4, Te6edf3freq_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Frequency (MHz)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='freq' type='text' placeholder='914.875' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3freq_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>e.g. 914.875, 868.000, 433.000</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Bandwidth — dropdown
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Bandwidth</label><select name='bw'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3BW_OPTIONS_COUNTTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint32_t Te6edf3bw_hz Tff7b72= Te6edf3pgm_read_dwordTb4b4b4(Tff7b72&Te6edf3BW_OPTIONS_HZTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4;
Tffa657char Te6edf3label_bufTb4b4b4[T79c0ff16Tb4b4b4]Tb4b4b4;
Te6edf3strncpy_PTb4b4b4(Te6edf3label_bufTb4b4b4, Tb4b4b4(Tff7b72const Tffa657charTff7b72*Tb4b4b4)Te6edf3pgm_read_ptrTb4b4b4(Tff7b72&Te6edf3BW_OPTIONS_LABELSTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3label_bufTb4b4b4)T79c0ff-1Tb4b4b4)Tb4b4b4;
Te6edf3label_bufTb4b4b4[Tff7b72sizeofTb4b4b4(Te6edf3label_bufTb4b4b4)T79c0ff-1Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3bw_hzTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3bw_hz Tff7b72=Tff7b72= Te6edf3cur_bwTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>Ta5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3label_bufTb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Spreading Factor — dropdown 5-12
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Spreading Factor</label><select name='sf'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3sf Tff7b72= T79c0ff5Tb4b4b4; Te6edf3sf Tff7b72<Tff7b72= T79c0ff12Tb4b4b4; Te6edf3sfTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3sfTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3sf Tff7b72=Tff7b72= Te6edf3cur_sfTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>SFTa5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3sfTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Coding Rate — dropdown 5-8 (maps to 4/5 through 4/8)
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Coding Rate</label><select name='cr'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3cr Tff7b72= T79c0ff5Tb4b4b4; Te6edf3cr Tff7b72<Tff7b72= T79c0ff8Tb4b4b4; Te6edf3crTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3crTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cr Tff7b72=Tff7b72= Te6edf3cur_crTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>4/Ta5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3crTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// TX Power
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>TX Power (dBm)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='txp' type='number' min='2' max='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72ifdef PA_MAX_OUTPUT
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3PA_MAX_OUTPUTTb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72else
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff22Ta5d6ff"Tb4b4b4;
Tff7b72#Tff7b72endif
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3cur_txpTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Tff7b72#Tff7b72ifdef PA_MAX_OUTPUT
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Max output for this board: Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3PA_MAX_OUTPUTTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff dBm (with PA)</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72endif

T8b949e// Airtime / duty-cycle limits — pause TX when measured airtime
T8b949e// exceeds these thresholds. 0 = disabled. Range 0–25% with 0.1%
T8b949e// resolution. Common preset: EU868 = 1% on the 1hr limit.
Tffa657char Te6edf3st_al_strTb4b4b4[T79c0ff16Tb4b4b4]Tb4b4b4;
Tffa657char Te6edf3lt_al_strTb4b4b4[T79c0ff16Tb4b4b4]Tb4b4b4;
Te6edf3dtostrfTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3st_airtime_limit Tff7b72* T79c0ff100.0fTb4b4b4, T79c0ff1Tb4b4b4, T79c0ff1Tb4b4b4, Te6edf3st_al_strTb4b4b4)Tb4b4b4;
Te6edf3dtostrfTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3lt_airtime_limit Tff7b72* T79c0ff100.0fTb4b4b4, T79c0ff1Tb4b4b4, T79c0ff1Tb4b4b4, Te6edf3lt_al_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='row'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><label>15s limit (%)</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='stal' type='number' step='0.1' min='0' max='25' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3st_al_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><label>1hr limit (%)</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<input name='ltal' type='number' step='0.1' min='0' max='25' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3lt_al_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Pause TX when measured LoRa airtime exceeds these limits. Ta5d6ff"
Ta5d6ff"Ta5d6ff0 = disabled. EU868 regulations suggest 1% on the 1hr limit.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── IFAC (Interface Access Code) Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f512; Network Access (IFAC)</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Set a network name and/or passphrase to restrict LoRa interface access. Ta5d6ff"
Ta5d6ff"Ta5d6ffOnly nodes with matching settings can communicate. Both fields are optional.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>IFAC</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='ifac_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Network Name</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='ifac_name' maxlength='32' placeholder='e.g. MyNetwork' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Passphrase</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input name='ifac_pass' type='password' maxlength='32' placeholder='Shared secret' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Device Advertisement Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x1f4cd; Device Advertisement</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p class='note'>Advertise this node and its parameters on the Reticulum network. Ta5d6ff"
Ta5d6ff"Ta5d6ffMaps such as <a href='https://rmap.world' target='_blank' style='color:#7ecfff'>rmap.world</a> Ta5d6ff"
Ta5d6ff"Ta5d6ffuse these announcements to automatically place a pin for the node. Ta5d6ff"
Ta5d6ff"Ta5d6ffDisabled by default; enable only if you want this node to be publicly listed.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Advertise Device</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='advert_en'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3advert_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3advert_enabledTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Latitude / Longitude — pre-populate with current values, but keep
T8b949e// the inputs blank when the user has not yet set coordinates so the
T8b949e// browser placeholder hint is visible.
Tffa657char Te6edf3lat_strTb4b4b4[T79c0ff32Tb4b4b4]Tb4b4b4;
Tffa657char Te6edf3lon_strTb4b4b4[T79c0ff32Tb4b4b4]Tb4b4b4;
Te6edf3lat_strTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Te6edf3lon_strTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3advert_enabled Tff7b72|Tff7b72|
Te6edf3firewall_stateTb4b4b4.Te6edf3advert_lat Tff7b72!Tff7b72= T79c0ff0.0 Tff7b72|Tff7b72|
Te6edf3firewall_stateTb4b4b4.Te6edf3advert_lon Tff7b72!Tff7b72= T79c0ff0.0Tb4b4b4) Tb4b4b4{
Te6edf3dtostrfTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3advert_latTb4b4b4, T79c0ff1Tb4b4b4, T79c0ff6Tb4b4b4, Te6edf3lat_strTb4b4b4)Tb4b4b4;
Te6edf3dtostrfTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3advert_lonTb4b4b4, T79c0ff1Tb4b4b4, T79c0ff6Tb4b4b4, Te6edf3lon_strTb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div class='row'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><label>Latitude (&deg;)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input id='advert_lat' name='advert_lat' type='text' inputmode='decimal' Ta5d6ff"
Ta5d6ff"Ta5d6ffplaceholder='e.g. 37.774929' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3lat_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<div><label>Longitude (&deg;)</label>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<input id='advert_lon' name='advert_lon' type='text' inputmode='decimal' Ta5d6ff"
Ta5d6ff"Ta5d6ffplaceholder='e.g. -122.419416' value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3lon_strTb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff'></div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</div>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Decimal degrees, signed. North/East positive, South/West negative. Ta5d6ff"
Ta5d6ff"Ta5d6ffLeave both blank to omit coordinates.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Randomize Offset</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='advert_jitter'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_stateTb4b4b4.Te6edf3advert_jitterTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Disabled</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3advert_jitterTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Enabled (~0.5 km / 0.5 mi)</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>When enabled, the advertised coordinates are shifted by a Ta5d6ff"
Ta5d6ff"Ta5d6ffrandom offset of approximately half a kilometre (about half a mile) for Ta5d6ff"
Ta5d6ff"Ta5d6ffprivacy. The exact stored coordinates are not changed.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Options Section ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<h2>&#x2699; Options</h2>Ta5d6ff"
Ta5d6ff"Ta5d6ff<label>Display Blanking</label>Ta5d6ff"
Ta5d6ff"Ta5d6ff<select name='disp_blank'>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

T8b949e// Read current blanking timeout from EEPROM (stored as minutes, 0 = never)
Tffa657uint8_t Te6edf3cur_blank Tff7b72= T79c0ff5Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BSETTb4b4b4)Tb4b4b4) Tff7b72=Tff7b72= Te6edf3CONF_OK_BYTETb4b4b4) Tb4b4b4{
Te6edf3cur_blank Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DBLKTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Tffa657uint8_t Te6edf3cur_rotation Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DROTTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72> T79c0ff3Tb4b4b4) Tb4b4b4{
Te6edf3cur_rotation Tff7b72= Te6edf3config_default_display_rotationTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Tff7b72static Tff7b72const Tffa657uint8_t Te6edf3blank_valsTb4b4b4[Tb4b4b4] Tff7b72= Tb4b4b4{ T79c0ff0Tb4b4b4, T79c0ff1Tb4b4b4, T79c0ff5Tb4b4b4, T79c0ff10Tb4b4b4, T79c0ff30Tb4b4b4, T79c0ff60 Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657charTff7b72* Te6edf3blank_labelsTb4b4b4[Tb4b4b4] Tff7b72= Tb4b4b4{ Ta5d6ff"Ta5d6ffNeverTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff1 minuteTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff5 minutesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff10 minutesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff30 minutesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff60 minutesTa5d6ff" Tb4b4b4}Tb4b4b4;
Tff7b72static Tff7b72const Tffa657int Te6edf3blank_count Tff7b72= T79c0ff6Tb4b4b4;

Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3blank_countTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3StringTb4b4b4(Te6edf3blank_valsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff'Ta5d6ff"Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3blank_valsTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Te6edf3cur_blankTb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff>Ta5d6ff"Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3blank_labelsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Turn off display after inactivity to save power</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<label>Display Orientation</label><select name='disp_rot'>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='0'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Landscape</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='1'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Portrait</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='2'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff2Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Landscape Flipped</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<option value='3'Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cur_rotation Tff7b72=Tff7b72= T79c0ff3Tb4b4b4) Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff selectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff>Portrait Flipped</option>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff</select>Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(Ta5d6ff"Ta5d6ff<p class='note'>Choose the orientation that matches your OLED mounting. Ta5d6ff"
Ta5d6ff"Ta5d6ffLandscape modes place the two status panes side by side; portrait modes stack them.</p>Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// ── Submit ──
Te6edf3html Tff7b72+Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<button type='submit'>Save &amp; Reboot</button>Ta5d6ff"
Ta5d6ff"Ta5d6ff</form></body></html>Ta5d6ff"
Tb4b4b4)Tb4b4b4;

Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendTb4b4b4(T79c0ff200Tb4b4b4, Ta5d6ff"Ta5d6fftext/htmlTa5d6ff"Tb4b4b4, Te6edf3htmlTb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Handle POST /save ──────────────────────────────────────────────────────

Tff7b72static Tffa657void Td2a8ffconfig_handle_saveTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// ── WiFi STA credentials ──
Te6edf3String Te6edf3ssid Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffssidTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3String Te6edf3psk Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffpskTa5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Write SSID to config EEPROM area
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3c Tff7b72= Tb4b4b4(Te6edf3i Tff7b72< Tb4b4b4(Tffa657intTb4b4b4)Te6edf3ssidTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72? Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72: T79c0ff0x00Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, Te6edf3cTb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ T79c0ff32Tb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;

T8b949e// Write PSK
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3c Tff7b72= Tb4b4b4(Te6edf3i Tff7b72< Tb4b4b4(Tffa657intTb4b4b4)Te6edf3pskTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72? Te6edf3pskTb4b4b4[Te6edf3iTb4b4b4] Tff7b72: T79c0ff0x00Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_PSK Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, Te6edf3cTb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_PSK Tff7b72+ T79c0ff32Tb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;

T8b949e// Set WiFi mode to STA
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_WIFITb4b4b4)Tb4b4b4, Te6edf3WR_WIFI_STATb4b4b4)Tb4b4b4;

T8b949e// Firewall mode always uses DHCP on the STA interface. Clear the legacy
T8b949e// static IP and netmask slots so stale values from older firmware or tools
T8b949e// cannot force a persistent static address.
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff4Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_IP Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_NM Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4, T79c0ff0x00Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ── WiFi enable setting ──
Te6edf3firewall_stateTb4b4b4.Te6edf3wifi_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffwifi_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// ── Display blanking (EEPROM stores minutes, 0 = disabled) ──
Tffa657int Te6edf3blank_minutes Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffdisp_blankTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3blank_minutes Tff7b72<Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3display_blanking_enabled Tff7b72= Tffa657falseTb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BSETTb4b4b4)Tb4b4b4, Te6edf3CONF_OK_BYTETb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DBLKTb4b4b4)Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tffa657uint8_t Te6edf3blank_val Tff7b72= Tb4b4b4(Tffa657uint8_tTb4b4b4)Tb4b4b4(Te6edf3blank_minutes Tff7b72> T79c0ff255 Tff7b72? T79c0ff255 Tff7b72: Te6edf3blank_minutesTb4b4b4)Tb4b4b4;
Te6edf3display_blanking_enabled Tff7b72= Tffa657trueTb4b4b4;
Te6edf3display_blanking_timeout Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3blank_val Tff7b72* T79c0ff60UL Tff7b72* T79c0ff1000ULTb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BSETTb4b4b4)Tb4b4b4, Te6edf3CONF_OK_BYTETb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DBLKTb4b4b4)Tb4b4b4, Te6edf3blank_valTb4b4b4)Tb4b4b4;
Tb4b4b4}

Tffa657int Te6edf3display_rotation Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffdisp_rotTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3display_rotation Tff7b72< T79c0ff0 Tff7b72|Tff7b72| Te6edf3display_rotation Tff7b72> T79c0ff3Tb4b4b4) Tb4b4b4{
Te6edf3display_rotation Tff7b72= Te6edf3config_default_display_rotationTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_DROTTb4b4b4)Tb4b4b4, Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3display_rotationTb4b4b4)Tb4b4b4;

T8b949e// ── TCP backbone settings ──
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3slot Tff7b72= T79c0ff0Tb4b4b4; Te6edf3slot Tff7b72< Te6edf3FIREWALL_BACKBONE_SLOTSTb4b4b4; Te6edf3slotTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3String Te6edf3en_arg Tff7b72= Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ffbbTa5d6ff"Tb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3slotTb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ff_enTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3String Te6edf3host_arg_name Tff7b72= Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ffbbTa5d6ff"Tb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3slotTb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ff_hostTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3String Te6edf3port_arg_name Tff7b72= Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ffbbTa5d6ff"Tb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3slotTb4b4b4) Tff7b72+ Te6edf3StringTb4b4b4(Ta5d6ff"Ta5d6ff_portTa5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Te6edf3en_argTb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

Te6edf3String Te6edf3bb_host Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Te6edf3host_arg_nameTb4b4b4)Tb4b4b4;
Te6edf3bb_hostTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3hostTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3hostTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3hostTb4b4b4, Te6edf3bb_hostTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3port Tff7b72= Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Te6edf3port_arg_nameTb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3port Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3port Tff7b72= T79c0ff4242Tb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3hostTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3firewall_stateTb4b4b4.Te6edf3backbonesTb4b4b4[Te6edf3slotTb4b4b4]Tb4b4b4.Te6edf3enabled Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}
Tb4b4b4}

T8b949e// ── Local TCP server settings ──
Te6edf3firewall_stateTb4b4b4.Te6edf3ap_tcp_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffap_tcp_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3firewall_stateTb4b4b4.Te6edf3ap_tcp_port Tff7b72= Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffap_tcp_portTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ap_tcp_port Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Te6edf3firewall_stateTb4b4b4.Te6edf3ap_tcp_port Tff7b72= T79c0ff4242Tb4b4b4;

T8b949e// ── IFAC settings ──
Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffifac_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

Te6edf3String Te6edf3ifac_name Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffifac_nameTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4, Te6edf3ifac_nameTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

Te6edf3String Te6edf3ifac_pass Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffifac_passTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4, Te6edf3ifac_passTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// If IFAC is enabled but both fields are empty, disable it
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_enabled Tff7b72&Tff7b72&
Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff' Tff7b72&Tff7b72&
Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3firewall_stateTb4b4b4.Te6edf3ifac_enabled Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}

T8b949e// ── Device advertisement settings ──
Te6edf3firewall_stateTb4b4b4.Te6edf3advert_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// Empty lat/lon strings are treated as "not set" → 0.0. Otherwise parse
T8b949e// and clamp to valid ranges; out-of-range values are silently coerced
T8b949e// to 0.0 rather than rejecting the whole save.
Te6edf3String Te6edf3lat_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_latTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3String Te6edf3lon_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_lonTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3lat_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3lon_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lat_argTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3firewall_stateTb4b4b4.Te6edf3advert_lat Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tffa657double Te6edf3lat_val Tff7b72= Te6edf3lat_argTb4b4b4.Te6edf3toDoubleTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lat_val Tff7b72< T79c0ff-90.0 Tff7b72|Tff7b72| Te6edf3lat_val Tff7b72> T79c0ff90.0 Tff7b72|Tff7b72| Te6edf3isnanTb4b4b4(Te6edf3lat_valTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3lat_val Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4}
Te6edf3firewall_stateTb4b4b4.Te6edf3advert_lat Tff7b72= Te6edf3lat_valTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3lon_argTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3firewall_stateTb4b4b4.Te6edf3advert_lon Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tffa657double Te6edf3lon_val Tff7b72= Te6edf3lon_argTb4b4b4.Te6edf3toDoubleTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lon_val Tff7b72< T79c0ff-180.0 Tff7b72|Tff7b72| Te6edf3lon_val Tff7b72> T79c0ff180.0 Tff7b72|Tff7b72| Te6edf3isnanTb4b4b4(Te6edf3lon_valTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3lon_val Tff7b72= T79c0ff0.0Tb4b4b4;
Tb4b4b4}
Te6edf3firewall_stateTb4b4b4.Te6edf3advert_lon Tff7b72= Te6edf3lon_valTb4b4b4;
Tb4b4b4}

Te6edf3firewall_stateTb4b4b4.Te6edf3advert_jitter Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffadvert_jitterTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// ── Node name ──
Te6edf3String Te6edf3node_name_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffnode_nameTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3node_name_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3node_nameTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3node_nameTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3strncpyTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3node_nameTb4b4b4, Te6edf3node_name_argTb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3node_nameTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// ── mDNS enable + hostname ──
Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffmdns_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4;
T8b949e// Lowercase, strip whitespace, allow only [a-z0-9-]; reject leading/trailing
T8b949e// hyphens. Empty input falls back to the auto-generated `rtnode<XXXX>` at
T8b949e// mDNS start time.
Tb4b4b4{
Te6edf3String Te6edf3mdns_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffmdns_nameTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3mdns_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3mdns_argTb4b4b4.Te6edf3toLowerCaseTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657char Te6edf3cleanTb4b4b4[T79c0ff33Tb4b4b4]Tb4b4b4;
Tffa657size_t Te6edf3j Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Tb4b4b4(Tffa657size_tTb4b4b4)Te6edf3mdns_argTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3j Tff7b72< Tff7b72sizeofTb4b4b4(Te6edf3cleanTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657char Te6edf3c Tff7b72= Te6edf3mdns_argTb4b4b4.Te6edf3charAtTb4b4b4(Te6edf3iTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tb4b4b4(Te6edf3c Tff7b72>Tff7b72= Ta5d6ff'Ta5d6ffaTa5d6ff' Tff7b72&Tff7b72& Te6edf3c Tff7b72<Tff7b72= Ta5d6ff'Ta5d6ffzTa5d6ff'Tb4b4b4) Tff7b72|Tff7b72| Tb4b4b4(Te6edf3c Tff7b72>Tff7b72= Ta5d6ff'Ta5d6ff0Ta5d6ff' Tff7b72&Tff7b72& Te6edf3c Tff7b72<Tff7b72= Ta5d6ff'Ta5d6ff9Ta5d6ff'Tb4b4b4) Tff7b72|Tff7b72| Te6edf3c Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff-Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3cleanTb4b4b4[Te6edf3jTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3cTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Te6edf3cleanTb4b4b4[Te6edf3jTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
T8b949e// Strip leading/trailing hyphens to keep RFC-952-ish compliance.
Tff7b72while Tb4b4b4(Te6edf3j Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3cleanTb4b4b4[Te6edf3j Tff7b72- T79c0ff1Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff-Ta5d6ff'Tb4b4b4) Te6edf3cleanTb4b4b4[Tff7b72-Tff7b72-Te6edf3jTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tffa657size_t Te6edf3start Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72while Tb4b4b4(Te6edf3cleanTb4b4b4[Te6edf3startTb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff-Ta5d6ff'Tb4b4b4) Te6edf3startTff7b72+Tff7b72+Tb4b4b4;
Te6edf3memsetTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_hostnameTb4b4b4, T79c0ff0Tb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_hostnameTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cleanTb4b4b4[Te6edf3startTb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3strncpyTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_hostnameTb4b4b4, Te6edf3clean Tff7b72+ Te6edf3startTb4b4b4,
Tff7b72sizeofTb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_hostnameTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

T8b949e// ── rnprobe responder ──
Te6edf3firewall_stateTb4b4b4.Te6edf3probe_enabled Tff7b72= Tb4b4b4(Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffprobe_enTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4)Tb4b4b4;

T8b949e// Save boundary config to EEPROM
Te6edf3firewall_save_configTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// ── LoRa radio settings ──
T8b949e// Parse frequency as integer Hz from a MHz decimal string (e.g. "869.4625"
T8b949e// or "869,4625" → 869462500) without floating-point loss.
Te6edf3String Te6edf3freq_str Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6fffreqTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3freq_strTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// Accept both '.' and ',' as decimal separator (locale-independent)
Te6edf3freq_strTb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff'Ta5d6ff,Ta5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff.Ta5d6ff'Tb4b4b4)Tb4b4b4;
Tffa657uint32_t Te6edf3freq_hz Tff7b72= T79c0ff0Tb4b4b4;
Tffa657int Te6edf3dot Tff7b72= Te6edf3freq_strTb4b4b4.Te6edf3indexOfTb4b4b4(Ta5d6ff'Ta5d6ff.Ta5d6ff'Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3dot Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// MHz integer part
Te6edf3freq_hz Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3freq_strTb4b4b4.Te6edf3substringTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3dotTb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72* T79c0ff1000000ULTb4b4b4;
T8b949e// Fractional part: pad/truncate to exactly 6 digits (Hz resolution)
Te6edf3String Te6edf3frac Tff7b72= Te6edf3freq_strTb4b4b4.Te6edf3substringTb4b4b4(Te6edf3dot Tff7b72+ T79c0ff1Tb4b4b4)Tb4b4b4;
Tff7b72while Tb4b4b4(Te6edf3fracTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72< T79c0ff6Tb4b4b4) Te6edf3frac Tff7b72+Tff7b72= Ta5d6ff'Ta5d6ff0Ta5d6ff'Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fracTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72> T79c0ff6Tb4b4b4) Te6edf3frac Tff7b72= Te6edf3fracTb4b4b4.Te6edf3substringTb4b4b4(T79c0ff0Tb4b4b4, T79c0ff6Tb4b4b4)Tb4b4b4;
Te6edf3freq_hz Tff7b72+Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3fracTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// No decimal point — treat as integer Hz
Te6edf3freq_hz Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3freq_strTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3freq_hz Tff7b72> T79c0ff0Tb4b4b4) Te6edf3lora_freq Tff7b72= Te6edf3freq_hzTb4b4b4;
Tb4b4b4}

Te6edf3String Te6edf3bw_str Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffbwTa5d6ff"Tb4b4b4)Tb4b4b4;
Tffa657uint32_t Te6edf3bw_val Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3bw_strTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3bw_val Tff7b72> T79c0ff0Tb4b4b4) Te6edf3lora_bw Tff7b72= Te6edf3bw_valTb4b4b4;

Tffa657int Te6edf3sf_val Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffsfTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3sf_val Tff7b72>Tff7b72= T79c0ff5 Tff7b72&Tff7b72& Te6edf3sf_val Tff7b72<Tff7b72= T79c0ff12Tb4b4b4) Te6edf3lora_sf Tff7b72= Te6edf3sf_valTb4b4b4;

Tffa657int Te6edf3cr_val Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffcrTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cr_val Tff7b72>Tff7b72= T79c0ff5 Tff7b72&Tff7b72& Te6edf3cr_val Tff7b72<Tff7b72= T79c0ff8Tb4b4b4) Te6edf3lora_cr Tff7b72= Te6edf3cr_valTb4b4b4;

Tffa657int Te6edf3txp_val Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6fftxpTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3txp_val Tff7b72>Tff7b72= T79c0ff2 Tff7b72&Tff7b72& Te6edf3txp_val Tff7b72<Tff7b72= T79c0ff30Tb4b4b4) Te6edf3lora_txp Tff7b72= Te6edf3txp_valTb4b4b4;

T8b949e// Airtime / duty-cycle limits. Empty / out-of-range / 0 = disabled.
T8b949e// firewall_save_config() has already run above, so write the bytes
T8b949e// directly here alongside the other LoRa parameters.
Tb4b4b4{
Te6edf3String Te6edf3stal_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffstalTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3String Te6edf3ltal_arg Tff7b72= Te6edf3config_serverTff7b72-Tff7b72>Te6edf3argTb4b4b4(Ta5d6ff"Ta5d6ffltalTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stal_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3ltal_argTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657float Te6edf3st_pct Tff7b72= Te6edf3stal_argTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72? Tb4b4b4(Tffa657floatTb4b4b4)Te6edf3stal_argTb4b4b4.Te6edf3toFloatTb4b4b4(Tb4b4b4) Tff7b72: T79c0ff0.0fTb4b4b4;
Tffa657float Te6edf3lt_pct Tff7b72= Te6edf3ltal_argTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4) Tff7b72? Tb4b4b4(Tffa657floatTb4b4b4)Te6edf3ltal_argTb4b4b4.Te6edf3toFloatTb4b4b4(Tb4b4b4) Tff7b72: T79c0ff0.0fTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3st_pct Tff7b72< T79c0ff0.0f Tff7b72|Tff7b72| Te6edf3isnanTb4b4b4(Te6edf3st_pctTb4b4b4)Tb4b4b4) Te6edf3st_pct Tff7b72= T79c0ff0.0fTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lt_pct Tff7b72< T79c0ff0.0f Tff7b72|Tff7b72| Te6edf3isnanTb4b4b4(Te6edf3lt_pctTb4b4b4)Tb4b4b4) Te6edf3lt_pct Tff7b72= T79c0ff0.0fTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3st_pct Tff7b72> T79c0ff25.0fTb4b4b4) Te6edf3st_pct Tff7b72= T79c0ff25.0fTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lt_pct Tff7b72> T79c0ff25.0fTb4b4b4) Te6edf3lt_pct Tff7b72= T79c0ff25.0fTb4b4b4;
Te6edf3firewall_stateTb4b4b4.Te6edf3st_airtime_limit Tff7b72= Te6edf3st_pct Tff7b72/ T79c0ff100.0fTb4b4b4;
Te6edf3firewall_stateTb4b4b4.Te6edf3lt_airtime_limit Tff7b72= Te6edf3lt_pct Tff7b72/ T79c0ff100.0fTb4b4b4;
Tffa657uint8_t Te6edf3st_byte Tff7b72= Tb4b4b4(Tffa657uint8_tTb4b4b4)Tb4b4b4(Te6edf3st_pct Tff7b72* T79c0ff10.0f Tff7b72+ T79c0ff0.5fTb4b4b4)Tb4b4b4;
Tffa657uint8_t Te6edf3lt_byte Tff7b72= Tb4b4b4(Tffa657uint8_tTb4b4b4)Tb4b4b4(Te6edf3lt_pct Tff7b72* T79c0ff10.0f Tff7b72+ T79c0ff0.5fTb4b4b4)Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_ST_ALTb4b4b4)Tb4b4b4, Te6edf3st_byteTb4b4b4)Tb4b4b4;
Te6edf3EEPROMTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_LT_ALTb4b4b4)Tb4b4b4, Te6edf3lt_byteTb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// Save LoRa config to EEPROM (reuse existing eeprom_conf functions)
T8b949e// Write directly since hw_ready may not be set yet
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_SFTb4b4b4)Tb4b4b4, Te6edf3lora_sfTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_CRTb4b4b4)Tb4b4b4, Te6edf3lora_crTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_TXPTb4b4b4)Tb4b4b4, Te6edf3lora_txpTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff0Tb4b4b4, Te6edf3lora_bw Tff7b72>Tff7b72> T79c0ff24Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff1Tb4b4b4, Te6edf3lora_bw Tff7b72>Tff7b72> T79c0ff16Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff2Tb4b4b4, Te6edf3lora_bw Tff7b72>Tff7b72> T79c0ff8Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_BWTb4b4b4) Tff7b72+ T79c0ff3Tb4b4b4, Te6edf3lora_bwTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff0Tb4b4b4, Te6edf3lora_freq Tff7b72>Tff7b72> T79c0ff24Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff1Tb4b4b4, Te6edf3lora_freq Tff7b72>Tff7b72> T79c0ff16Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff2Tb4b4b4, Te6edf3lora_freq Tff7b72>Tff7b72> T79c0ff8Tb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_FREQTb4b4b4) Tff7b72+ T79c0ff3Tb4b4b4, Te6edf3lora_freqTb4b4b4)Tb4b4b4;
Te6edf3eeprom_updateTb4b4b4(Te6edf3eeprom_addrTb4b4b4(Te6edf3ADDR_CONF_OKTb4b4b4)Tb4b4b4, Te6edf3CONF_OK_BYTETb4b4b4)Tb4b4b4;

Te6edf3EEPROMTb4b4b4.Te6edf3commitTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// ── Send confirmation page ──
Te6edf3String Te6edf3ok Tff7b72= Te6edf3FTb4b4b4(
Ta5d6ff"Ta5d6ff<!DOCTYPE html><html><head>Ta5d6ff"
Ta5d6ff"Ta5d6ff<meta name='viewport' content='width=device-width,initial-scale=1'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<title>Saved</title>Ta5d6ff"
Ta5d6ff"Ta5d6ff<style>Ta5d6ff"
Ta5d6ff"Ta5d6ffbody{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;padding:40px;Ta5d6ff"
Ta5d6ff"Ta5d6ffdisplay:flex;align-items:center;justify-content:center;min-height:80vh;}Ta5d6ff"
Ta5d6ff"Ta5d6ff.ok{background:#16213e;padding:30px;border-radius:12px;text-align:center;max-width:400px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffh1{color:#4caf50;margin-bottom:16px;}Ta5d6ff"
Ta5d6ff"Ta5d6ffp{color:#aaa;}Ta5d6ff"
Ta5d6ff"Ta5d6ff</style></head><body>Ta5d6ff"
Ta5d6ff"Ta5d6ff<div class='ok'>Ta5d6ff"
Ta5d6ff"Ta5d6ff<h1>&#x2705; Configuration Saved</h1>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p>Device will reboot in 3 seconds and connect to your WiFi network.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff<p style='color:#666;font-size:0.85em;'>If the device cannot connect, hold the button for 5+ seconds to re-enter setup.</p>Ta5d6ff"
Ta5d6ff"Ta5d6ff</div></body></html>Ta5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendTb4b4b4(T79c0ff200Tb4b4b4, Ta5d6ff"Ta5d6fftext/htmlTa5d6ff"Tb4b4b4, Te6edf3okTb4b4b4)Tb4b4b4;

T8b949e// Give the response time to send
Te6edf3delayTb4b4b4(T79c0ff3000Tb4b4b4)Tb4b4b4;

T8b949e// Reboot
Te6edf3ESPTb4b4b4.Te6edf3restartTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Captive Portal redirect ─────────────────────────────────────────────────
Tff7b72static Tffa657void Td2a8ffconfig_handle_redirectTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendHeaderTb4b4b4(Ta5d6ff"Ta5d6ffLocationTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffhttp://10.0.0.1/Ta5d6ff"Tb4b4b4, Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3sendTb4b4b4(T79c0ff302Tb4b4b4, Ta5d6ff"Ta5d6fftext/plainTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRedirecting to setup...Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Check if config is needed ───────────────────────────────────────────────
Tffa657bool Td2a8ffboundary_needs_configTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// If the RTNode app marker is missing, this node was either never
T8b949e// configured by RTNode or was flashed from a different firmware family
T8b949e// such as stock RNode. Force the portal so RTNode can claim and rewrite
T8b949e// its persisted settings explicitly.
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3firewall_app_marker_validTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}

T8b949e// Check if WiFi SSID is configured
Tffa657char Te6edf3ssidTb4b4b4[T79c0ff33Tb4b4b4]Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< T79c0ff32Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_SSID Tff7b72+ Te6edf3iTb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72=Tff7b72= Tb4b4b4(Tffa657charTb4b4b4)T79c0ff0xFFTb4b4b4) Te6edf3ssidTb4b4b4[Te6edf3iTb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Te6edf3ssidTb4b4b4[T79c0ff32Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;

T8b949e// Also check firewall mode enable flag
Tffa657uint8_t Te6edf3bmode Tff7b72= Te6edf3EEPROMTb4b4b4.Te6edf3readTb4b4b4(Te6edf3config_addrTb4b4b4(Te6edf3ADDR_CONF_BMODETb4b4b4)Tb4b4b4)Tb4b4b4;

T8b949e// Need config if no SSID set and boundary not yet configured
Tff7b72if Tb4b4b4(Te6edf3ssidTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff' Tff7b72&Tff7b72& Te6edf3bmode Tff7b72!Tff7b72= Te6edf3FIREWALL_ENABLE_BYTETb4b4b4) Tb4b4b4{
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}
Tff7b72return Tffa657falseTb4b4b4;
Tb4b4b4}

T8b949e// ─── Start Config Portal ─────────────────────────────────────────────────────
Tffa657void Td2a8ffconfig_portal_startTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3config_portal_activeTb4b4b4) Tff7b72returnTb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Starting configuration portal...Ta5d6ff"Tb4b4b4)Tb4b4b4;

T8b949e// Tear down any STA-mode mDNS before flipping the radio to AP — the
T8b949e// ESPmDNS state must not survive a WiFi mode change.
Te6edf3mdns_serviceTff7b72:Tff7b72:Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// Stop any existing WiFi
Te6edf3WiFiTb4b4b4.Te6edf3softAPdisconnectTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3disconnectTb4b4b4(Tffa657trueTb4b4b4, Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3modeTb4b4b4(Te6edf3WIFI_MODE_NULLTb4b4b4)Tb4b4b4;
Te6edf3delayTb4b4b4(T79c0ff100Tb4b4b4)Tb4b4b4;

T8b949e// Start AP
Te6edf3WiFiTb4b4b4.Te6edf3modeTb4b4b4(Te6edf3WIFI_APTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3softAPTb4b4b4(Te6edf3CONFIG_AP_SSIDTb4b4b4, Tffa657NULLTb4b4b4)Tb4b4b4; T8b949e// Open AP for easy setup
Te6edf3delayTb4b4b4(T79c0ff150Tb4b4b4)Tb4b4b4;

Te6edf3IPAddress Te6edf3ap_addrTb4b4b4(T79c0ff10Tb4b4b4, T79c0ff0Tb4b4b4, T79c0ff0Tb4b4b4, T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3IPAddress Te6edf3ap_maskTb4b4b4(T79c0ff255Tb4b4b4, T79c0ff255Tb4b4b4, T79c0ff255Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3softAPConfigTb4b4b4(Te6edf3ap_addrTb4b4b4, Te6edf3ap_addrTb4b4b4, Te6edf3ap_maskTb4b4b4)Tb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ff[Config] AP started: Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Te6edf3CONFIG_AP_SSIDTb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ff[Config] IP: Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3softAPIPTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;

T8b949e// Start DNS server for captive portal (redirect all domains to us)
Te6edf3config_dns Tff7b72= Te6edf3new Te6edf3DNSServerTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3config_dnsTff7b72-Tff7b72>Te6edf3startTb4b4b4(Te6edf3DNS_PORTTb4b4b4, Ta5d6ff"Ta5d6ff*Ta5d6ff"Tb4b4b4, Te6edf3ap_addrTb4b4b4)Tb4b4b4;

T8b949e// Start web server
Te6edf3config_server Tff7b72= Te6edf3new Te6edf3WebServerTb4b4b4(Te6edf3HTTP_PORTTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3onTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4, Te6edf3HTTP_GETTb4b4b4, Te6edf3config_send_htmlTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3onTb4b4b4(Ta5d6ff"Ta5d6ff/saveTa5d6ff"Tb4b4b4, Te6edf3HTTP_POSTTb4b4b4, Te6edf3config_handle_saveTb4b4b4)Tb4b4b4;
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3onNotFoundTb4b4b4(Te6edf3config_handle_redirectTb4b4b4)Tb4b4b4; T8b949e// Captive portal catch-all
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3beginTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// Publish a stable .local name so users don't need to remember the AP IP.
T8b949e// Captive portal still works via DNSServer for clients without mDNS.
Tff7b72if Tb4b4b4(Te6edf3firewall_stateTb4b4b4.Te6edf3mdns_enabledTb4b4b4) Tb4b4b4{
Te6edf3mdns_serviceTff7b72:Tff7b72:Te6edf3start_ap_configTb4b4b4(Ta5d6ff"Ta5d6ffrtnodeTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3config_portal_active Tff7b72= Tffa657trueTb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Portal ready — connect to WiFi: Ta5d6ff" Tff7b72+ Te6edf3StringTb4b4b4(Te6edf3CONFIG_AP_SSIDTb4b4b4)Tb4b4b4)Tb4b4b4;

Tff7b72#Tff7b72if HAS_DISPLAY
Tff7b72if Tb4b4b4(Te6edf3disp_readyTb4b4b4) Tb4b4b4{
T8b949e// Show config mode on display
Te6edf3stat_areaTb4b4b4.Te6edf3fillScreenTb4b4b4(Te6edf3SSD1306_BLACKTb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setTextColorTb4b4b4(Te6edf3SSD1306_WHITETb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setTextSizeTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setTextWrapTb4b4b4(Tffa657falseTb4b4b4)Tb4b4b4;

Te6edf3stat_areaTb4b4b4.Te6edf3setCursorTb4b4b4(Tb4b4b4(T79c0ff64 Tff7b72- Tb4b4b4(T79c0ff6 Tff7b72* T79c0ff6Tb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff2Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ffCONFIGTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setCursorTb4b4b4(Tb4b4b4(T79c0ff64 Tff7b72- Tb4b4b4(T79c0ff4 Tff7b72* T79c0ff6Tb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff2Tb4b4b4, T79c0ff8Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ffMODETa5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3stat_areaTb4b4b4.Te6edf3setCursorTb4b4b4(Tb4b4b4(T79c0ff64 Tff7b72- Tb4b4b4(T79c0ff10 Tff7b72* T79c0ff6Tb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff2Tb4b4b4, T79c0ff24Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ffConnect toTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setCursorTb4b4b4(Tb4b4b4(T79c0ff64 Tff7b72- Tb4b4b4(T79c0ff10 Tff7b72* T79c0ff6Tb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff2Tb4b4b4, T79c0ff32Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ffWifi SSID:Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setCursorTb4b4b4(Tb4b4b4(T79c0ff64 Tff7b72- Tb4b4b4(T79c0ff7 Tff7b72* T79c0ff6Tb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff2Tb4b4b4, T79c0ff40Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ffRTNode-Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3setCursorTb4b4b4(Tb4b4b4(T79c0ff64 Tff7b72- Tb4b4b4(T79c0ff5 Tff7b72* T79c0ff6Tb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff2Tb4b4b4, T79c0ff48Tb4b4b4)Tb4b4b4;
Te6edf3stat_areaTb4b4b4.Te6edf3printTb4b4b4(Ta5d6ff"Ta5d6ffSetupTa5d6ff"Tb4b4b4)Tb4b4b4;

Te6edf3displayTb4b4b4.Te6edf3clearDisplayTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3displayTb4b4b4.Te6edf3drawBitmapTb4b4b4(T79c0ff0Tb4b4b4, T79c0ff0Tb4b4b4, Te6edf3stat_areaTb4b4b4.Te6edf3getBufferTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3stat_areaTb4b4b4.Te6edf3widthTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3stat_areaTb4b4b4.Te6edf3heightTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3SSD1306_WHITETb4b4b4, Te6edf3SSD1306_BLACKTb4b4b4)Tb4b4b4;
Te6edf3displayTb4b4b4.Te6edf3displayTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72#Tff7b72endif
T8b949e// Headless: LED ramp will be driven from the WCC portal loop
Tff7b72if Tb4b4b4(Te6edf3headless_modeTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Headless mode — LED will breathe during config portalTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

T8b949e// ─── Stop Config Portal ──────────────────────────────────────────────────────
Tffa657void Td2a8ffconfig_portal_stopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3config_portal_activeTb4b4b4) Tff7b72returnTb4b4b4;

Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[Config] Stopping configuration portalTa5d6ff"Tb4b4b4)Tb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3config_serverTb4b4b4) Tb4b4b4{
Te6edf3config_serverTff7b72-Tff7b72>Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3delete Te6edf3config_serverTb4b4b4;
Te6edf3config_server Tff7b72= Tffa657nullptrTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3config_dnsTb4b4b4) Tb4b4b4{
Te6edf3config_dnsTff7b72-Tff7b72>Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3delete Te6edf3config_dnsTb4b4b4;
Te6edf3config_dns Tff7b72= Tffa657nullptrTb4b4b4;
Tb4b4b4}

Te6edf3mdns_serviceTff7b72:Tff7b72:Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;

Te6edf3WiFiTb4b4b4.Te6edf3softAPdisconnectTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3WiFiTb4b4b4.Te6edf3modeTb4b4b4(Te6edf3WIFI_MODE_NULLTb4b4b4)Tb4b4b4;
Te6edf3config_portal_active Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}

T8b949e// ─── Portal Loop — call from main loop() ─────────────────────────────────────
Tffa657void Td2a8ffconfig_portal_loopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3config_portal_activeTb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3config_dnsTb4b4b4) Te6edf3config_dnsTff7b72-Tff7b72>Te6edf3processNextRequestTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3config_serverTb4b4b4) Te6edf3config_serverTff7b72-Tff7b72>Te6edf3handleClientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Is portal active? ──────────────────────────────────────────────────────
Tffa657bool Td2a8ffconfig_portal_is_activeTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3config_portal_activeTb4b4b4;
Tb4b4b4}

Tff7b72#Tff7b72endif T8b949e// FIREWALL_MODE
Tff7b72#Tff7b72endif T8b949e// FIREWALL_CONFIG_H

Served by rngit 1.4.2 - Generated in 0.11s